/**
 * Copyright (C) 2016 Eshore Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.eshore.thrpc.client;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.SocketException;

import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.thrift.transport.TTransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.eshore.thrpc.RpcException;
import com.eshore.thrpc.common.ServerNode;

/**
 * 默认invoker实现
 * <p>
 * 
 * @author : zhouzhixiang
 * @date : 2015-6-3
 */
public class DefaultInvoker<T> implements Invoker {

    /** LOGGER */
    private final Logger LOGGER = LoggerFactory.getLogger(getClass());

    /** {@link GenericKeyedObjectPool}<{@link ServerNode},{@link T}> */
    private final GenericKeyedObjectPool<ServerNode, T> pool;

    /** 重试次数 */
    private final int retry;
    /** client端的{@link ServerNode} */
    private final ServerNode servertNode;

    /**
     * 创建代理实现类，使用连接池获取thrift客户端连接
     * <p>
     *
     */
    public DefaultInvoker(ServerNode servertNode, GenericKeyedObjectPool<ServerNode, T> pool,
            int retry) {
        this.pool = pool;
        this.retry = retry;
        this.servertNode = servertNode;
    }

    @Override
    public Object invoke(Method method, Object[] args) throws RpcException {
        T client = null;
        ServerNode serverNode = servertNode;
        Throwable exception = null;

        for (int i = 0; i == 0 || i < retry + 1; i++) {
            try {
                client = pool.borrowObject(servertNode);
                Object result = method.invoke(client, args);
                return result;
            } catch (InvocationTargetException ite) {// XXX:InvocationTargetException异常发生在method.invoke()中
                Throwable cause = ite.getCause();
                pool.clear(serverNode);
                if (cause != null) {
                    if (cause instanceof TTransportException) {
                        exception = cause;
                        try {
                            // XXX:这里直接清空pool,否则会出现连接慢恢复的现象
                            // 发送socket异常时，证明socket已经失效，需要重新创建
                            if (cause.getCause() != null
                                    && cause.getCause() instanceof SocketException) {
                                LOGGER.error("socket exception !!!!!!", cause);
                                pool.clear();
                            } else {
                                // XXX:其他异常的情况，需要将当前链接置为无效
                                pool.invalidateObject(serverNode, client);
                            }
                        } catch (Exception e) {
                            LOGGER.error(
                                    "client request to server error , And clear client from the pool!",
                                    e);
                        }
                        client = null; // 这里是为了防止后续return回pool中
                    } else {
                        LOGGER.error("server may be error!", ite);
                        exception = cause;
                    }
                } else {
                    exception = ite;
                }
            } catch (Throwable e) {
                LOGGER.error("client request to server error!", e);
                exception = e;
            } finally {
                if (client != null) {
                    try {
                        pool.returnObject(serverNode, client);
                    } catch (Exception e) {
                        LOGGER.error(e.getMessage(), e);
                    }
                }
            }
        }
        throw new RpcException("Request error!", exception);
    }
}
